#include "Pic.h"
#include "utility/Instruction.h"

/// @brief initialize programmable interrupt code
void InitPIC() { 
    // need to init ICW4 and cascaded mode
    Outb(PIC0_ICW1, PIC_ICW1_ICW4 | PIC_ICW1_ALWAYS_1 | PIC_CASCADED);
    Outb(PIC0_ICW2, IRQ_PIC_START);
    Outb(PIC0_ICW3, PIC_CASCADED_PORT);
    Outb(PIC0_ICW4, PIC_ICW4_8086);

    Outb(PIC1_ICW1, PIC_ICW1_ICW4 | PIC_ICW1_ALWAYS_1 | PIC_CASCADED);
    Outb(PIC1_ICW2, IRQ_PIC_START + 8);
    Outb(PIC1_ICW3, PIC_CASCADED_PORT_NUM);
    Outb(PIC1_ICW4, PIC_ICW4_8086);

    // only enable interruption from PIC1
    Outb(PIC0_IMR, 0xFF & ~(1 << PIC_CASCADED_PORT_NUM));
    // disable all interruption
    Outb(PIC1_IMR, 0xFF);
}

/// @brief Send End Of Interrupt (EOI) signal to the PIC.
/// @param irq The IRQ number that has been serviced.
void SendPicEOI(int irq)
{
    /// This function notifies the Programmable Interrupt Controller (PIC) that the interrupt
    /// with the given IRQ number has been handled. If the IRQ is from the slave PIC (IRQ >= IRQ_PIC_START + 8),
    /// it sends an EOI to both the slave and master PICs. Otherwise, it only sends an EOI to the master PIC.
    if (irq>= IRQ_PIC_START + 8)
    {
        Outb(PIC1_OCW2, PIC_OCW2_EOI);
    }
    
    Outb(PIC0_OCW2, PIC_OCW2_EOI);
}

void EnablePicIrq(int irq)
{
    if (irq < IRQ_PIC_START)
    {
        return;
    }
    
    irq -= IRQ_PIC_START;
    int port;
    if (irq < 8)
    {
        port = PIC0_IMR;
    }
    else
    {
        port = PIC1_IMR;
    }
    
    uint8_t mask = Inb(port) & ~(1 << irq);
    Outb(port, mask);
}

void DisablePicIrq(int irq)
{
    if (irq < IRQ_PIC_START)
    {
        return;
    }
    
    irq -= IRQ_PIC_START;
    int port;
    if (irq < 8)
    {
        port = PIC0_IMR;
    }
    else
    {
        port = PIC1_IMR;
    }
    
    uint8_t mask = Inb(port) | (1 << irq);
    Outb(port, mask);
}